package se.webbzon.oschi01.collision3d;

import java.util.ArrayList;
import java.util.Collection;

import javax.media.j3d.BoundingBox;
import javax.media.j3d.Bounds;
import javax.media.j3d.Transform3D;
import javax.vecmath.Point3d;

public class CollisionModel3D extends CollisionNode3D {
	
	// A collection of collision triangles of this model
	private final Collection<CollisionTriangle3D> triangles;
	
	// An allocation used to detect collisions without wasting memory
	private final CollisionAllocation3D allocation;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new 3d collision model by using the given collection
	 * of CollisionTriangle3D. **/
	public CollisionModel3D(Collection<CollisionTriangle3D> triangles) {
		super(generateBoundary(triangles));
		this.triangles = triangles;
		this.allocation = new CollisionAllocation3D();
		for (CollisionTriangle3D triangle : triangles) {
			triangle.setTransform(getTransform());
		}
	}
	
	/** Creates a new 3d collision model by using the given transform and
	 * collection of CollisionTriangle3D. **/
	public CollisionModel3D(Transform3D transform, 
							Collection<CollisionTriangle3D> triangles) {
		this(triangles);
		setTransform(transform);
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns true if this collision model intersects another collision model. **/
	@Override public boolean intersects(CollisionNode3D other) {
		if (super.intersects(other)) {
			if (other.isModel()) {
				CollisionModel3D otherModel = (CollisionModel3D) other;
				for (CollisionTriangle3D thisTriangle : triangles)
					for (CollisionTriangle3D otherTriangle : otherModel.triangles)
						if (thisTriangle.intersects(otherTriangle,allocation))
							return true;
			} else {
				for (int i = 0; i < other.children(); i++)
					if (intersects(other.getChild(i)))
						return true;
			}	
		}
		return false;
	}
	
	/** Transform the collision model permanently. **/
	@Override public void transform(Transform3D transform) {
		super.transform(transform);
		for (CollisionTriangle3D triangle : triangles)
			triangle.transform(transform);
	}
	
	/** Merges this collision model with another collision model. 
	 * The merging will be applied to this instance. **/
	public void merge(CollisionModel3D other) {
		super.merge(other);
		for (CollisionTriangle3D triangle : other.triangles) {
			CollisionTriangle3D t = (CollisionTriangle3D) triangle.clone();
			triangles.add(t);
			t.setTransform(getTransform());
		}
	}
	
	@Override public boolean isModel() {
		return true;
	}

	@Override public int children() {
		return 0;
	}

	@Override public CollisionNode3D getChild(int n) {
		return null;
	}
	
	@Override public Object clone() {
		ArrayList<CollisionTriangle3D> triangles = 
				new ArrayList<CollisionTriangle3D>(this.triangles.size());
		for (CollisionTriangle3D triangle : this.triangles)
			triangles.add((CollisionTriangle3D) triangle.clone());
		return new CollisionModel3D(triangles);	
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Generates a boundary for the super constructor. **/
	public static Bounds generateBoundary(Collection<CollisionTriangle3D> triangles) {
		double	xMin = Double.MAX_VALUE, yMin = Double.MAX_VALUE, 
				zMin = Double.MAX_VALUE, xMax = Double.MIN_NORMAL, 
				yMax = Double.MIN_VALUE, zMax = Double.MIN_VALUE;
		final Point3d[] edges = new Point3d[] {
					new Point3d(), 
					new Point3d(), 
					new Point3d()
				};
		for (CollisionTriangle3D triangle : triangles) {
			triangle.getEdges(edges);
			xMin = Math.min(Math.min(Math.min(xMin, edges[0].x),edges[1].x),edges[2].x);
			yMin = Math.min(Math.min(Math.min(yMin, edges[0].y),edges[1].y),edges[2].y);
			zMin = Math.min(Math.min(Math.min(zMin, edges[0].z),edges[1].z),edges[2].z);
			xMax = Math.max(Math.max(Math.max(xMax, edges[0].x),edges[1].x),edges[2].x);
			yMax = Math.max(Math.max(Math.max(yMax, edges[0].y),edges[1].y),edges[2].y);
			zMax = Math.max(Math.max(Math.max(zMax, edges[0].z),edges[1].z),edges[2].z);
		}
		return new BoundingBox(new Point3d(xMin,yMin,zMin),new Point3d(xMax,yMax,zMax));
	}
	
}
